
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package noder

import (
	"bytes"
	"fmt"
	"internal/goversion"
	"io"
	"runtime"
	"sort"

	"cmd/compile/internal/base"
	"cmd/compile/internal/inline"
	"cmd/compile/internal/ir"
	"cmd/compile/internal/typecheck"
	"cmd/compile/internal/types"
	"cmd/compile/internal/types2"
	"cmd/internal/src"
)

// localPkgReader保存用于读取本地
// 包的包读取器。它的存在使得统一IR链接器可以在以后引用它
// 。
var localPkgReader *pkgReader

// 统一从语法的AST构造本地包的IR。
// 
// 管道包含两个步骤：
// 
// （1）生成包导出数据“存根”。
// 
// （2）根据包裹导出数据生成包裹IR。
// 
// 步骤（1）中的包数据“存根”包含本地包、
// 中的所有内容，但没有导入任何内容。当我们将导出数据写入输出文件时（请参阅WriteNeWeExport函数），我们运行“链接器”，它会执行
// 一些操作：
// 
// /+更新编译器扩展数据（例如，内联成本、转义分析结果）。
// 
// /+处理任何可传递依赖项的重新导出。
// 
// +删除任何不必要的细节（例如，不可内联的函数，因为任何
// /下游进口商只关心可内联的函数）。
// 
// 源文件进行两次类型检查，一次在写入导出数据之前
// 使用types2 checker，一次在使用gc/typecheck读取导出数据之后。
// 一旦我们总是使用types2 checker，这种重复工作就会消失，
// 我们可以删除gc/typecheck通行证。它仍然存在的原因是：
// 
// /+它降低了维护一个类型检查分支的工程成本
// （例如，不需要像CL 327651这样的后台修复）。
// 
// /+这使得通过toolstash-cmp更容易。
// 
// +历史上，我们总是在导入后重新运行typechecker，即使
// 我们知道导入的数据是有效的。这并不理想，但也不会导致任何
// 问题。
// 
// +在gc/类型检查过程中仍在进行转换，比如重写
// 多值函数调用或转换ir。OINDEX->ir。看地图。
// 
// 使用语法+类型2树，它已经有了泛型的完整表示，
// 统一IR有完整类型的AST，用于在步骤（1）中进行内省。
// 换句话说，我们拥有构建通用IR表单
// （参见writer.captureVars获取示例）所需的所有信息。
func unified(noders []*noder) {
	inline.NewInline = InlineCall

	if !quirksMode() {
		writeNewExportFunc = writeNewExport
	} else if base.Flag.G != 0 {
		base.Errorf("cannot use -G and -d=quirksmode together")
	}

	newReadImportFunc = func(data string, pkg1 *types.Pkg, ctxt *types2.Context, packages map[string]*types2.Package) (pkg2 *types2.Package, err error) {
		pr := newPkgDecoder(pkg1.Path, data)

		// 读取types2和编译器后端的包描述符。我们已经通过基地了。旗帜Lang to types2处理验证
		readPackage(newPkgReader(pr), pkg1)
		pkg2 = readPackage2(ctxt, packages, pr)
		return
	}

	data := writePkgStub(noders)

	// 用户的源代码。现在把它升级到当前版本，重新解析
	// 这样，如果我们构造IR，
	// 使用了更新的Go特性，那么typecheck就不会抱怨了。
	base.Flag.Lang = fmt.Sprintf("go1.%d", goversion.Version)
	types.ParseLangFlag()

	assert(types.LocalPkg.Path == "")
	types.LocalPkg.Height = 0 // 复位，使pkgReader复位。pkgIdx不会抱怨
	target := typecheck.Target

	typecheck.TypecheckAllowed = true

	localPkgReader = newPkgReader(newPkgDecoder(types.LocalPkg.Path, data))
	readPackage(localPkgReader, types.LocalPkg)

	r := localPkgReader.newReader(relocMeta, privateRootIdx, syncPrivate)
	r.pkgInit(types.LocalPkg, target)

	// 键入检查任何顶级作业。我们在这里忽略非赋值
	// 因为其他声明是在构造
	// 时进行类型检查的。
	for i, ndecls := 0, len(target.Decls); i < ndecls; i++ {
		switch n := target.Decls[i]; n.Op() {
		case ir.OAS, ir.OAS2:
			target.Decls[i] = typecheck.Stmt(n)
		}
	}

	// 不要使用range——bodyIdx可以向机器人添加闭包。
	for len(todoBodies) > 0 {
		// 我们扩展身体的顺序无关紧要，所以从末端弹出
		// 如果身体进一步增长，减少对机器人的重新分配。
		fn := todoBodies[len(todoBodies)-1]
		todoBodies = todoBodies[:len(todoBodies)-1]

		pri, ok := bodyReader[fn]
		assert(ok)
		pri.funcBody(fn)

		// 实例化的泛型函数：添加到Decls以进行类型检查
		// 和编译。
		if fn.OClosure == nil && len(pri.dict.targs) != 0 {
			target.Decls = append(target.Decls, fn)
		}
	}
	todoBodies = nil
	todoBodiesDone = true

	// 检查是否没有任何内容通过了打字检查。
	for _, n := range target.Decls {
		if n.Typecheck() == 0 {
			base.FatalfAt(n.Pos(), "missed typecheck: %v", n)
		}

		// 对于函数，请检查至少它们的第一条语句（如果
		// 有）是否也进行了类型检查。
		if fn, ok := n.(*ir.Func); ok && len(fn.Body) != 0 {
			if stmt := fn.Body[0]; stmt.Typecheck() == 0 {
				base.FatalfAt(stmt.Pos(), "missed typecheck: %v", stmt)
			}
		}
	}

	base.ExitIfErrors() // 以防
}

// writePkgStub type检查给定的解析源文件，
// 写入代表它们的导出数据包存根，
// 并返回结果。
func writePkgStub(noders []*noder) string {
	m, pkg, info := checkFiles(noders)

	pw := newPkgWriter(m, pkg, info)

	pw.collectDecls(noders)

	publicRootWriter := pw.newWriter(relocMeta, syncPublic)
	privateRootWriter := pw.newWriter(relocMeta, syncPrivate)

	assert(publicRootWriter.idx == publicRootIdx)
	assert(privateRootWriter.idx == privateRootIdx)

	{
		w := publicRootWriter
		w.pkg(pkg)
		w.bool(false) // 已初始化；XXX 

		scope := pkg.Scope()
		names := scope.Names()
		w.len(len(names))
		for _, name := range scope.Names() {
			w.obj(scope.Lookup(name), nil)
		}

		w.sync(syncEOF)
		w.flush()
	}

	{
		w := privateRootWriter
		w.pkgInit(noders)
		w.flush()
	}

	var sb bytes.Buffer // TODO（mdempsky）：字符串。在#44505被解析为
	pw.dump(&sb)

	// /之后，我们就完成了类型2。确保该包已被
	// 垃圾回收。
	freePackage(pkg)

	return sb.String()
}

// freePackage确保给定的包被垃圾收集。
func freePackage(pkg *types2.Package) {
	// 下面的GC测试依赖于一个精确的GC，一旦无法访问对象，它就会作为
	// 运行终结器。我们的实现提供了
	// 这一点，但其他/较旧的实现可能没有（例如，Go 1.4提供了
	// 不是因为#22350）。为了避免对GOROOT_引导工具链施加不必要的限制，我们在引导过程中跳过了测试
	// 。
	if base.CompilerBootstrap {
		return
	}

	done := make(chan struct{})
	runtime.SetFinalizer(pkg, func(*types2.Package) { close(done) })

	// 重要提示：循环中涉及的对象未最终确定，因此零
	// 退出pkg以中断其循环并允许终结器运行。
	*pkg = types2.Package{}

	// 释放pkg通常只需要1到2个周期，但再尝试几次也无妨。
	for i := 0; i < 10; i++ {
		select {
		case <-done:
			return
		default:
			runtime.GC()
		}
	}

	base.Fatalf("package never finalized")
}

func readPackage(pr *pkgReader, importpkg *types.Pkg) {
	r := pr.newReader(relocMeta, publicRootIdx, syncPublic)

	pkg := r.pkg()
	assert(pkg == importpkg)

	if r.bool() {
		sym := pkg.Lookup(".inittask")
		task := ir.NewNameAt(src.NoXPos, sym)
		task.Class = ir.PEXTERN
		sym.Def = task
	}

	for i, n := 0, r.len(); i < n; i++ {
		r.sync(syncObject)
		assert(!r.bool())
		idx := r.reloc(relocObj)
		assert(r.len() == 0)

		path, name, code := r.p.peekObj(idx)
		if code != objStub {
			objReader[types.NewPkg(path, "").Lookup(name)] = pkgReaderIndex{pr, idx, nil}
		}
	}
}

func writeNewExport(out io.Writer) {
	l := linker{
		pw: newPkgEncoder(),

		pkgs:  make(map[string]int),
		decls: make(map[*types.Sym]int),
	}

	publicRootWriter := l.pw.newEncoder(relocMeta, syncPublic)
	assert(publicRootWriter.idx == publicRootIdx)

	var selfPkgIdx int

	{
		pr := localPkgReader
		r := pr.newDecoder(relocMeta, publicRootIdx, syncPublic)

		r.sync(syncPkg)
		selfPkgIdx = l.relocIdx(pr, relocPkg, r.reloc(relocPkg))

		r.bool() // 已初始化

		for i, n := 0, r.len(); i < n; i++ {
			r.sync(syncObject)
			assert(!r.bool())
			idx := r.reloc(relocObj)
			assert(r.len() == 0)

			xpath, xname, xtag := pr.peekObj(idx)
			assert(xpath == pr.pkgPath)
			assert(xtag != objStub)

			if types.IsExported(xname) {
				l.relocIdx(pr, relocObj, idx)
			}
		}

		r.sync(syncEOF)
	}

	{
		var idxs []int
		for _, idx := range l.decls {
			idxs = append(idxs, idx)
		}
		sort.Ints(idxs)

		w := publicRootWriter

		w.sync(syncPkg)
		w.reloc(relocPkg, selfPkgIdx)

		w.bool(typecheck.Lookup(".inittask").Def != nil)

		w.len(len(idxs))
		for _, idx := range idxs {
			w.sync(syncObject)
			w.bool(false)
			w.reloc(relocObj, idx)
			w.len(0)
		}

		w.sync(syncEOF)
		w.flush()
	}

	l.pw.dump(out)
}
